package ru.picman;

import ru.picman.ui.UnitPainter;
import ru.picman.util.EasyMath;
import android.graphics.Point;

/**
 * The Unit class is the base class for the Bird, Pig and any other
 * moving character in the game. It contains basic functions to move
 * the characters, while keeping the units aligned with the tiles and
 * refraining them from walking through walls.
 * 
 * @author Matthijs
 */
public abstract class Unit
                      extends Entity
{
	
	/**
	 * Constructs a new Unit of the specified Type. An Painter will
	 * be created for the unit and the unit will be initialized.
	 * 
	 * @param type The type of the Unit
	 * @throws IllegalStateException If the type is not the type of
	 *             a movable character
	 */
	public Unit(EntityType type) {
		super(type);
		checkType();
		setPainter(new UnitPainter(this));
	}
	
	//
	
	/**
	 * Moves the Unit in the current direction. If for some reason
	 * the unit cannot be moved in the set direction, the direction
	 * will be set to DIRECTION_NONE and {@link #hit(int, int)} will
	 * be called. At every move-call the Unit will be - if this is 
	 * necessary - aligned to the grid. 
	 */
	public void move() {
		checkDirection();
		float moveX = 0, moveY = 0;
		switch (currentDirection) {
			case DIRECTION_NONE:
				break;
			case DIRECTION_UP:
				moveY = -moveStep;
				break;
			case DIRECTION_DOWN:
				moveY = moveStep;
				break;
			case DIRECTION_LEFT:
				moveX = -moveStep;
				break;
			case DIRECTION_RIGHT:
				moveX = moveStep;
				break;
			default:
				throw new RuntimeException("Doesn't throw");
		}
		if (moveX == moveY)
			return;
		
		move(moveX,moveY);
		++steps;
	}
	
	/**
	 * Moves the unit the specified (dx,dy), but only if the target tile
	 * is not blocked. If the tile is blocked, an hit event will be fired
	 * and the desired direction will be set to DIRECTION_NONE.
	 * @param dx The delta x to move
	 * @param dy The delta y to move
	 */
	private void move(float dx, float dy) {
		// Check if there is a block at the next tile the unit will *touch*, before actually moving it
		float toX = getX() + dx, toY = getY() + dy;
		int nextX = (int)(toX + (int)(dx + .99f)), nextY = (int)(toY + (int)(dy + .99f));
		int width = world.getWidth(), height = world.getHeight();
		boolean hit = false;
		if (EasyMath.isBetween(nextX,-1,width) && EasyMath.isBetween(nextY,-1,height) && !world.isFree(new Point(nextX,nextY)))
			hit = true;
		else if (EasyMath.isBetween(nextX,-1,width)) {
			toY = (nextY <= -1 ? height : (nextY > height ? -1 : toY));
			nextY = EasyMath.clamp((int)toY,0,height - 1);
			if (!world.isFree(new Point(nextX,nextY)))
				hit = true;
		}
		else if (EasyMath.isBetween(nextY,-1,height)) {
			toX = (nextX <= -1 ? width : (nextX > width ? -1 : toX));
			nextX = EasyMath.clamp((int)toX,0,width - 1);
			if (!world.isFree(new Point(nextX,nextY)))
				hit = true;
		}
		
		if (hit) {
			setDesiredDirection(DIRECTION_NONE);
			hit(nextX,nextY);
			return;
		}
		Point p = getPositionI();
		if (p.x != toX || p.y != toY)
			moved(p.x,p.y);
		setPosition(toX,toY);
	}
	
	/**
	 * Sets the desired direction to the specified direction. The actual 
	 * direction will be set to the desired direction the moment this is
	 * possible.
	 * @param direction The direction to set
	 */
	public void setDesiredDirection(int direction) {
		if (direction < -1 || direction > 3)
			throw new IllegalArgumentException();
		desiredDirection = direction;
	}
	
	/**
	 * Returns the current direction.
	 * 
	 * @return The current direction of this unit
	 */
	public int getDirection() {
		return currentDirection;
	}
	
	/**
	 * Returns the set desired direction.
	 * 
	 * @return The desired direction
	 */
	public int getDesiredDirection() {
		return desiredDirection;
	}
	
	/**
	 * Sets the move step of this unit. The move step is the step
	 * a unit makes each time it moves. A higher move step results
	 * in a faster moving unit. The default is zero.
	 * 
	 * @param moveStep The move step that is desired
	 */
	public void setMoveStep(float moveStep) {
		System.out.println("setting movestep to "+moveStep);
		this.moveStep = moveStep;
		this.stepsForOne = (int)(1 / moveStep);
		this.steps = 0;
		alignPosition();
	}
	
	/**
	 * Returns the set move step.
	 * @return The move step
	 */
	public float getMoveStep() {
		return moveStep;
	}
	
	/**
	 * The unit has hit a wall.
	 * 
	 * @param x The x position of the wall block
	 * @param y The y position of the wall block
	 */
	public void hit(int x, int y) {}

	/**
	 * The unit has found an egg.
	 * 
	 * @param x The x position of the egg
	 * @param y The y position of the egg
	 */
	public void found(int x, int y) {}
	
	/**
	 * The unit has been moved.
	 * 
	 * @param oldX The old x position of the unit
	 * @param oldY The old y position of the unit
	 */
	public void moved(int oldX, int oldY) {}
	
	//
	
	/**
	 * Checks if the current direction is the same as the desired direction,
	 * after aligning with the tiles. If this is not the case, the current
	 * direction will be set to the desired direction - if possible.
	 */
	private void checkDirection() {
		if (steps % stepsForOne != 0 && desiredDirection != DIRECTION_NONE && currentDirection != DIRECTION_NONE)
			return;
		alignPosition();
		Point p = getPositionI();
		p.y += (desiredDirection == DIRECTION_UP ? -1 : (desiredDirection == DIRECTION_DOWN ? 1 : 0));
		p.x += (desiredDirection == DIRECTION_LEFT ? -1 : (desiredDirection == DIRECTION_RIGHT ? 1 : 0));
		if (EasyMath.isBetween(p.x,-1,world.getWidth()) && EasyMath.isBetween(p.y,-1,world.getHeight()) && world.isFree(p))
			currentDirection = desiredDirection;
	}
	
	/**
	 * Quick check on the unit type. This should be a BIRD or a PIG.
	 */
	private void checkType() {
		switch (getType()) {
			case BIRD:
			case PIG:
				break;
			default:
				throw new IllegalStateException("This is not a valid unit");
		}
	}
	
	//
	
	private int             desiredDirection = DIRECTION_NONE;
	private int             currentDirection = DIRECTION_NONE;
	private int             steps;
	private int             stepsForOne;
	private float           moveStep;
	
	public static final int DIRECTION_NONE   = -1;
	public static final int DIRECTION_UP     = 0;
	public static final int DIRECTION_LEFT   = 1;
	public static final int DIRECTION_DOWN   = 2;
	public static final int DIRECTION_RIGHT  = 3;
	
}
